package com.lbj.mvvm.base

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lbj.mvvm.livedata.SingleLiveEvent
import com.lbj.mvvm.network.AppException
import com.lbj.mvvm.network.BaseResponse
import com.lbj.mvvm.network.ExceptionHandle
import kotlinx.coroutines.*

open class JetpackVM : ViewModel() {

    val defUI: UIChange by lazy { UIChange() }

    /**
     * 所有网络请求都在 viewModelScope 域中启动，当页面销毁时会自动
     * 调用ViewModel的  #onCleared 方法取消所有协程
     */
    private fun launchUI(block: suspend CoroutineScope.() -> Unit) {
        viewModelScope.launch {
            block()
        }
    }

    /**
     * 过滤请求结果，其他全抛异常 回调在ViewModel
     * [block] 请求体
     * [success] 成功回调
     * [errorCall] 失败回调
     * [isShowDialog] 是否显示加载框
     * [loadingMessage] 加载框提示内容
     */
    fun <T> launchRequestVM(
        block: suspend CoroutineScope.() -> BaseResponse<T>,
        success: (T) -> Unit,
        errorCall: (AppException) -> Unit = {},
        isShowDialog: Boolean = false,
        loadingMessage: String = "请求网络中..."
    ) {
        if (isShowDialog) defUI.showDialog.postValue(loadingMessage)
        launchUI {
            handleException(
                { withContext(Dispatchers.IO) { block() } },
                { res -> executeResponse(res) { success(it) } },
                { errorCall(it) },
                { defUI.dismissDialog.call() }
            )
        }
    }

    /**
     * 过滤请求结果，其他全抛异常 回调在ViewModel
     * [block] 请求体
     * [success] 成功回调并返回response的code及msg
     * [errorCall] 失败回调
     * [isShowDialog] 是否显示加载框
     * [loadingMessage] 加载框提示内容
     */
    fun <T> launchRequestVMMsg(
        block: suspend CoroutineScope.() -> BaseResponse<T>,
        success: (T, Int, String) -> Unit,
        errorCall: (AppException) -> Unit = {},
        isShowDialog: Boolean = false,
        loadingMessage: String = "请求网络中..."
    ) {
        if (isShowDialog) defUI.showDialog.postValue(loadingMessage)
        launchUI {
            handleException(
                { withContext(Dispatchers.IO) { block() } },
                { res -> executeResponse(res) { success(it, res.returnCode, res.msg ?: "") } },
                { errorCall(it) },
                { defUI.dismissDialog.call() }
            )
        }
    }

    /**
     * 不过滤请求结果，其他全抛异常 回调在Viewmodel
     * [block] 请求体
     * [success] 成功回调
     * [errorCall] 失败回调
     * [isShowDialog] 是否显示加载框
     * [loadingMessage] 加载框提示内容
     */
    fun <T> launchResultVMNoCheck(
        block: suspend CoroutineScope.() -> T,
        success: (T) -> Unit,
        errorCall: (AppException) -> Unit = {},
        isShowDialog: Boolean = false,
        loadingMessage: String = "请求网络中..."
    ) {
        if (isShowDialog) defUI.showDialog.postValue(loadingMessage)
        launchUI {
            handleExceptionNoCheck(
                { withContext(Dispatchers.IO) { block() } },
                { res -> executeResponseNoCheck(res) { success(it) } },
                { errorCall(it) },
                { defUI.dismissDialog.call() }
            )
        }
    }

    /**
     * 请求结果过滤，判断请求服务器请求结果是否成功，不成功则会抛出异常
     */
    private suspend fun <T> executeResponse(
        response: BaseResponse<T>,
        success: suspend CoroutineScope.(T) -> Unit
    ) {
        coroutineScope {
            if (response.isSuccess()) success(response.data)
            else throw AppException(response.returnCode, response.msg)
        }
    }

    /**
     * 请求结果过滤，判断请求服务器请求结果是否成功，不成功则会抛出异常
     */
    private suspend fun <T> executeResponseNoCheck(
        response: T,
        success: suspend CoroutineScope.(T) -> Unit
    ) {
        coroutineScope {
            success(response)
        }
    }

    /**
     * 异常统一处理
     * [block] 请求体
     * [success]成功回调
     * [error] 失败回调
     * [complete] 最终结果回调 （一般不用）
     */
    private suspend fun <T> handleException(
        block: suspend CoroutineScope.() -> BaseResponse<T>,
        success: suspend CoroutineScope.(BaseResponse<T>) -> Unit,
        error: suspend CoroutineScope.(AppException) -> Unit,
        complete: suspend CoroutineScope.() -> Unit
    ) {
        coroutineScope {
            try {
                success(block())
            } catch (e: Throwable) {
                error(ExceptionHandle.handleException(e))
            } finally {
                complete()
            }
        }
    }

    /**
     * 异常统一处理
     * [block] 请求体
     * [success] 成功回调
     * [error] 失败回调
     * [complete] 最终结果回调 （一般不用）
     */
    private suspend fun <T> handleExceptionNoCheck(
        block: suspend CoroutineScope.() -> T,
        success: suspend CoroutineScope.(T) -> Unit,
        error: suspend CoroutineScope.(AppException) -> Unit,
        complete: suspend CoroutineScope.() -> Unit
    ) {
        coroutineScope {
            try {
                success(block())
            } catch (e: Throwable) {
                error(ExceptionHandle.handleException(e))
            } finally {
                complete()
            }
        }
    }

    /**
     * UI事件
     */
    inner class UIChange {
        //显示加载框
        val showDialog by lazy { SingleLiveEvent<String>() }

        //隐藏
        val dismissDialog by lazy { SingleLiveEvent<Void>() }
    }
}